/*
*
* Copyright (C) 2007-2015 Licensed to the Comunes Association (CA) under
* one or more contributor license agreements (see COPYRIGHT for details).
* The CA licenses this file to you under the GNU Affero General Public
* License version 3, (the "License"); you may not use this file except in
* compliance with the License. This file is part of kune.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Source: http://www.zackgrossbart.com/hackito/gwt-rest-auto/
*
******************************************************************************/
package cc.kune.core.client.sitebar.search;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cc.kune.common.client.log.Log;
import cc.kune.common.client.notify.NotifyUser;
import cc.kune.common.shared.i18n.I18n;
import cc.kune.common.shared.i18n.I18nTranslationService;
import cc.kune.core.shared.SearcherConstants;
import cc.kune.core.shared.dto.GroupType;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestOracle.Callback;
import com.google.gwt.user.client.ui.SuggestOracle.Request;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase;
// TODO: Auto-generated Javadoc
/**
* A SuggestBox that uses REST and allows for multiple values, autocomplete and
* browsing.
*
* @author Bess Siegal <bsiegal@novell.com>
*/
public class MultivalueSuggestBox extends Composite implements SelectionHandler<Suggestion>, Focusable,
KeyUpHandler {
/**
* Bean for name-value pairs.
*
* @author vjrj@ourproject.org (Vicente J. Ruiz Jurado)
*/
private class Option {
private String mname;
private String mvalue;
/**
* No argument constructor
*/
public Option() {
}
/**
* @return Returns the name.
*/
public String getName() {
return mname;
}
/**
* @return Returns the value.
*/
public String getValue() {
return mvalue;
}
/**
* @param name
* The name to set.
*/
public void setName(final String name) {
mname = name;
}
/**
* @param value
* The value to set.
*/
public void setValue(final String value) {
mvalue = value;
}
}
/**
* An abstract class that handles success and error conditions from the REST
* call
*/
private abstract class OptionQueryCallback {
abstract void error(Throwable exception);
abstract void success(OptionResultSet optResults);
}
/**
* Bean for total size and options
*/
protected class OptionResultSet {
/** JSON key for DisplayName */
public static final String DISPLAY_NAME = "longName";
/** JSON key for Options */
public static final String OPTIONS = "list";
/** JSON key for the size of the Results */
public static final String TOTAL_SIZE = "size";
/** JSON key for Value */
public static final String VALUE = "shortName";
private final List<Option> m_options = new ArrayList<Option>();
private int mtotalSize;
/**
* Constructor. Must pass in the total size.
*
* @param totalSize
* the total size of the template
*/
public OptionResultSet(final int totalSize) {
setTotalSize(totalSize); // NOPMD by vjrj on 4/05/11 19:45
}
/**
* Add an option
*
* @param option
* - the Option to add
*/
public void addOption(final Option option) {
m_options.add(option);
}
/**
* @return an array of Options
*/
public Option[] getOptions() {
return m_options.toArray(new Option[m_options.size()]);
}
/**
* @return Returns the totalSize.
*/
public int getTotalSize() {
return mtotalSize;
}
/**
* @param totalSize
* The totalSize to set.
*/
public void setTotalSize(final int totalSize) {
mtotalSize = totalSize;
}
}
/**
* A bean to serve as a custom suggestion so that the value is available and
* the replace will look like it is supporting multivalues
*/
class OptionSuggestion implements SuggestOracle.Suggestion {
static final String NEXT_VALUE = "NEXT";
static final String PREVIOUS_VALUE = "PREVIOUS";
private String mdisplay;
private String mname;
private final String mreplace;
private final String mvalue;
/**
* Constructor for navigation options
*
* @param nav
* - next or previous value
* @param currentTextValue
* - the current contents of the text box
*/
OptionSuggestion(final String nav, final String currentTextValue) {
if (NEXT_VALUE.equals(nav)) {
mdisplay = "<div class=\"autocompleterNext\" title=\"Next\"></div>";
} else {
mdisplay = "<div class=\"autocompleterPrev\" title=\"Previous\"></div>";
}
mreplace = currentTextValue;
mvalue = nav;
}
/**
* Constructor for regular options
*
* @param displ
* - the name of the option
* @param val
* - the value of the option
* @param replacePre
* - the current contents of the text box
* @param query
* - the query
*/
OptionSuggestion(final String displ, final String val, final String replacePre, final String query) {
mname = displ;
final int begin = displ.toLowerCase().indexOf(query.toLowerCase());
if (begin >= 0) {
final int end = begin + query.length();
final String match = displ.substring(begin, end);
mdisplay = displ.replaceFirst(match, "<b>" + match + "</b>");
} else {
// may not necessarily be a part of the query, for example if "*" was
// typed.
mdisplay = displ;
}
mreplace = getFullReplaceText(displ, replacePre);
mvalue = val;
}
@Override
public String getDisplayString() {
return mdisplay;
}
/**
* Get the name of the option. (when not multivalued, this will be the same
* as getReplacementString)
*
* @return name
*/
public String getName() {
return mname;
}
@Override
public String getReplacementString() {
return mreplace;
}
/**
* Get the value of the option
*
* @return value
*/
public String getValue() {
return mvalue;
}
}
/**
* A custom callback that has the original SuggestOracle.Request and
* SuggestOracle.Callback
*/
private class RestSuggestCallback extends OptionQueryCallback {
private final SuggestOracle.Callback m_callback;
private final String m_query; // this may be different from
// m_request.getQuery when multivalued it's
// only the substring after the last delimiter
private final SuggestOracle.Request m_request;
RestSuggestCallback(final Request request, final Callback callback, final String query) {
m_request = request;
m_callback = callback;
m_query = query;
}
@Override
public void error(final Throwable exception) {
updateFormFeedback(FormFeedback.ERROR, "Invalid: " + m_query);
}
@Override
public void success(final OptionResultSet optResults) {
final SuggestOracle.Response resp = new SuggestOracle.Response();
final List<OptionSuggestion> suggs = new ArrayList<OptionSuggestion>();
final int totSize = optResults.getTotalSize();
if (totSize < 1) {
// if there were no suggestions, then it's an invalid value
updateFormFeedback(FormFeedback.ERROR, "Invalid: " + m_query);
if (showNoResult) {
final OptionSuggestion sugg = new OptionSuggestion(i18n.t("No results"), "#",
m_request.getQuery(), m_query);
suggs.add(sugg);
}
} else if (false && totSize == 1) {
// Patch to show always the suggestions
// it's an exact match, so do not bother with showing suggestions,
final Option o = optResults.getOptions()[0];
final String displ = o.getName();
// remove the last bit up to separator
// mfield.setText(getFullReplaceText(displ, m_request.getQuery()));
Log.info("RestSuggestCallback.success! exact match found for displ = " + displ);
// onExactMatch.onExactMatch(o.getValue());
// it's valid!
updateFormFeedback(FormFeedback.VALID, null);
// set the value into the valueMap
putValue(displ, o.getValue());
} else {
// more than 1 so show the suggestions
// if not at the first page, show PREVIOUS
if (mindexFrom > 0) {
final OptionSuggestion prev = new OptionSuggestion(OptionSuggestion.PREVIOUS_VALUE,
m_request.getQuery());
suggs.add(prev);
}
// show the suggestions
for (final Option o : optResults.getOptions()) {
final OptionSuggestion sugg = new OptionSuggestion(o.getName(), o.getValue(),
m_request.getQuery(), m_query);
suggs.add(sugg);
}
// if there are more pages, show NEXT
if (mindexTo < totSize) {
final OptionSuggestion next = new OptionSuggestion(OptionSuggestion.NEXT_VALUE,
m_request.getQuery());
suggs.add(next);
}
// nothing has been picked yet, so let the feedback show an error
// (unsaveable)
updateFormFeedback(FormFeedback.ERROR, "Invalid: " + m_query);
}
// it's ok (and good) to pass an empty suggestion list back to the suggest
// box's callback method
// the list is not shown at all if the list is empty.
resp.setSuggestions(suggs);
m_callback.onSuggestionsReady(m_request, resp);
}
}
/*
* Some custom inner classes for our SuggestOracle
*/
/**
* A custom Suggest Oracle
*/
private class RestSuggestOracle extends SuggestOracle {
private SuggestOracle.Callback mcallback;
private SuggestOracle.Request mrequest;
private final Timer mtimer;
RestSuggestOracle() {
mtimer = new Timer() {
@Override
public void run() {
/*
* The reason we check for empty string is found at
* http://development.lombardi.com/?p=39 -- paraphrased, if you
* backspace quickly the contents of the field are emptied but a query
* for a single character is still executed. Workaround for this is to
* check for an empty string field here.
*/
if (!mfield.getText().trim().isEmpty()) {
if (misMultivalued) {
// calling this here in case a user is trying to correct the "kev"
// value of Allison Andrews, Kev, Josh Nolan or pasted in multiple
// values
findExactMatches();
}
getSuggestions();
}
}
};
}
private void getSuggestions() {
String query = mrequest.getQuery();
// find the last thing entered up to the last separator
// and use that as the query
if (misMultivalued) {
final int sep = query.lastIndexOf(DISPLAY_SEPARATOR);
if (sep > 0) {
query = query.substring(sep + DISPLAY_SEPARATOR.length());
}
}
query = query.trim();
// do not query if it's just an empty String
// also do not get suggestions you've already got an exact match for this
// string in the m_valueMap
if (query.length() > 0 && mvalueMap.get(query) == null) {
// JSUtil.println("getting Suggestions for: " + query);
updateFormFeedback(FormFeedback.LOADING, null);
queryOptions(query, mindexFrom, mindexTo, new RestSuggestCallback(mrequest, mcallback, query));
}
}
@Override
public boolean isDisplayStringHTML() {
return true;
}
@Override
public void requestSuggestions(final SuggestOracle.Request request,
final SuggestOracle.Callback callback) {
// This is the method that gets called by the SuggestBox whenever some
// types into the text field
mrequest = request;
mcallback = callback;
// reset the indexes (b/c NEXT and PREV call getSuggestions directly)
resetPageIndices();
// If the user keeps triggering this event (e.g., keeps typing), cancel
// and restart the timer
mtimer.cancel();
mtimer.schedule(DELAY);
}
}
private static final int DELAY = 500;
private static final String DISPLAY_SEPARATOR = ", ";
private static final int FIND_EXACT_MATCH_QUERY_LIMIT = 20;
private static final int PAGE_SIZE = 15;
private static final String VALUE_DELIM = ";";
/**
* Returns a String without the last delimiter
*
* @param str
* - String to trim
* @param delim
* - the delimiter
* @return the String without the last delimter
*/
private static String trimLastDelimiter(String str, final String delim) { // NOPMD
// by
// vjrj
// on
// 4/05/11
// 19:46
if (str.length() > 0) {
str = str.substring(0, str.length() - delim.length());
}
return str;
}
private final I18nTranslationService i18n;
private com.google.gwt.http.client.Request lastQuery;
private final FormFeedback mfeedback;
private final SuggestBox mfield;
private int mfindExactMatchesFound = 0;
private final ArrayList<String> mfindExactMatchesNot = new ArrayList<String>();
private int mfindExactMatchesTotal = 0;
private int mindexFrom = 0;
private int mindexTo = 0;
private boolean misMultivalued = false;
private String mrestEndpointUrl;
private final Map<String, String> mvalueMap;
private final boolean showNoResult;
// private final OnExactMatch onExactMatch;
/**
* Constructor.
*
* @param i18n
*
* @param the
* URL for the REST endpoint. This URL should accept the parameters q
* (for query), indexFrom and indexTo
* @param isMultivalued
* - true for allowing multiple values
* @param onExactMatch
* @param showNoResult
* if we have to show noResult message when the search is empty or
* not
*/
public MultivalueSuggestBox(final I18nTranslationService i18n, final boolean showNoResult,
final String restEndpointUrl, final boolean isMultivalued, final OnExactMatch onExactMatch) {
this.i18n = i18n;
this.showNoResult = showNoResult;
mrestEndpointUrl = restEndpointUrl;
misMultivalued = isMultivalued;
// this.onExactMatch = onExactMatch;
final FlowPanel panel = new FlowPanel();
TextBoxBase textfield;
if (isMultivalued) {
panel.addStyleName("textarearow");
textfield = new TextArea();
} else {
panel.addStyleName("textfieldrow");
textfield = new TextBox();
}
// Create our own SuggestOracle that queries REST endpoint
final SuggestOracle oracle = new RestSuggestOracle();
// intialize the SuggestBox
mfield = new SuggestBox(oracle, textfield);
if (isMultivalued) {
// have to do this here b/c gwt suggest box wipes
// style name if added in previous if
textfield.addStyleName("multivalue");
}
mfield.addStyleName("wideTextField");
mfield.addSelectionHandler(this);
mfield.addKeyUpHandler(this);
panel.add(mfield);
mfeedback = new FormFeedback();
// panel.add(mfeedback);
initWidget(panel);
/*
* Create a Map that holds the values that should be stored. It will be
* keyed on "display value", so that any time a "display value" is added or
* removed the valueMap can be updated.
*/
mvalueMap = new HashMap<String, String>();
resetPageIndices();
}
// private final OnExactMatch onExactMatch;
private void findExactMatch(final String displayValue, final int position) {
updateFormFeedback(FormFeedback.LOADING, null);
queryOptions(displayValue, 0, FIND_EXACT_MATCH_QUERY_LIMIT, // return a
// relatively
// small amount
// in case
// wanted "Red"
// and
// "Brick Red"
// is the first
// thing
// returned
new OptionQueryCallback() {
@Override
public void error(final Throwable exception) {
// an exact match couldn't be found, just increment not found
mfindExactMatchesNot.add(displayValue);
finalizeFindExactMatches();
}
private void extactMatchFound(final int position, final Option option) {
putValue(option.getName(), option.getValue());
Log.info("extactMatchFound ! exact match found for displ = " + displayValue);
// onExactMatch.onExactMatch(option.getValue());
// and replace the text
final String text = mfield.getText();
final String[] keys = text.split(DISPLAY_SEPARATOR.trim());
keys[position] = option.getName();
String join = "";
for (final String n : keys) {
join += n.trim() + DISPLAY_SEPARATOR;
}
join = trimLastDelimiter(join, DISPLAY_SEPARATOR);
// Commented mfield.setText(join);
mfindExactMatchesFound++;
}
private void finalizeFindExactMatches() {
if (mfindExactMatchesFound + mfindExactMatchesNot.size() == mfindExactMatchesTotal) {
// when the found + not = total, we're done
if (mfindExactMatchesNot.size() > 0) {
String join = "";
for (final String val : mfindExactMatchesNot) {
join += val.trim() + DISPLAY_SEPARATOR;
}
join = trimLastDelimiter(join, DISPLAY_SEPARATOR);
updateFormFeedback(FormFeedback.ERROR, "Invalid:" + join);
} else {
updateFormFeedback(FormFeedback.VALID, null);
}
}
}
@Override
public void success(final OptionResultSet optResults) {
final int totSize = optResults.getTotalSize();
if (totSize == 1) {
// an exact match was found, so place it in the value map
final Option option = optResults.getOptions()[0];
extactMatchFound(position, option);
} else {
// try to find the exact matches within the results
boolean found = false;
for (final Option option : optResults.getOptions()) {
if (displayValue.equalsIgnoreCase(option.getName())) {
extactMatchFound(position, option);
found = true;
break;
}
}
if (!found) {
mfindExactMatchesNot.add(displayValue);
Log.info("RestExactMatchCallback -- exact match not found for displ = " + displayValue);
}
}
finalizeFindExactMatches();
}
});
}
/**
* If there is more than one key in the text field, check that every key has a
* value in the map. For any that do not, try to find its exact match.
*/
private void findExactMatches() {
final String text = mfield.getText();
final String[] keys = text.split(DISPLAY_SEPARATOR.trim());
final int len = keys.length;
if (len < 2) {
// do not continue. if there's 1, it is the last one, and getSuggestions
// can handle it
return;
}
mfindExactMatchesTotal = 0;
mfindExactMatchesFound = 0;
mfindExactMatchesNot.clear();
for (int pos = 0; pos < len; pos++) {
final String key = keys[pos].trim();
if (!key.isEmpty()) {
final String v = mvalueMap.get(key);
if (null == v) {
mfindExactMatchesTotal++;
}
}
}
// then loop through again and try to find them
/*
* We may have invalid values due to a multi-value copy-n-paste, or going
* back and messing with a middle or first key; so for each invalid value,
* try to find an exact match. *
*/
for (int pos = 0; pos < len; pos++) {
final String key = keys[pos].trim();
if (!key.isEmpty()) {
final String v = mvalueMap.get(key);
if (null == v) {
findExactMatch(key, pos);
}
}
}
}
private String getFullReplaceText(final String displ, String replacePre) { // NOPMD
// by
// vjrj
// on
// 4/05/11
// 19:45
// replace the last bit after the last comma
if (replacePre.lastIndexOf(DISPLAY_SEPARATOR) > 0) {
replacePre = replacePre.substring(0, replacePre.lastIndexOf(DISPLAY_SEPARATOR))
+ DISPLAY_SEPARATOR;
} else {
replacePre = "";
}
// then add a comma
if (misMultivalued) {
return replacePre + displ + DISPLAY_SEPARATOR;
} else {
return displ;
}
}
public SuggestBox getSuggestBox() {
return mfield;
}
@Override
public int getTabIndex() {
return mfield.getTabIndex();
}
/**
* Get the value(s) as a String. If allowing multivalues, separated by the
* VALUE_DELIM
*
* @return value(s) as a String
*/
public String getValue() {
// String together all the values in the valueMap
// based on the display values shown in the field
final String text = mfield.getText();
String values = "";
String invalids = "";
String newKeys = "";
if (misMultivalued) {
final String[] keys = text.split(DISPLAY_SEPARATOR);
for (String key : keys) {
key = key.trim();
if (!key.isEmpty()) {
final String v = mvalueMap.get(key);
Log.info("getValue for key = " + key + " is v = " + v);
if (null != v) {
values += v + VALUE_DELIM;
// rebuild newKeys removing invalids and dups
newKeys += key + DISPLAY_SEPARATOR;
} else {
invalids += key + DISPLAY_SEPARATOR;
}
}
}
values = trimLastDelimiter(values, VALUE_DELIM);
// set the new display values
mfield.setText(newKeys);
} else {
values = mvalueMap.get(text);
}
// if there were any invalid show warning
if (!invalids.isEmpty()) {
// trim last separator
invalids = trimLastDelimiter(invalids, DISPLAY_SEPARATOR);
updateFormFeedback(FormFeedback.ERROR, "Invalids: " + invalids);
}
return values;
}
/**
* Get the value map
*
* @return value map
*/
public Map<String, String> getValueMap() {
return mvalueMap;
}
@Override
public void onKeyUp(final KeyUpEvent event) {
/*
* Because SuggestOracle.requestSuggestions does not get called when the
* text field is empty this key up handler is necessary for handling the
* case when there is an empty text field... Here, the FormFeedback is
* reset.
*/
updateFormFeedback(FormFeedback.NONE, null);
}
@Override
public void onSelection(final SelectionEvent<Suggestion> event) {
final Suggestion suggestion = event.getSelectedItem();
if (suggestion instanceof OptionSuggestion) {
final OptionSuggestion osugg = (OptionSuggestion) suggestion;
// if NEXT or PREVIOUS were selected, requery but bypass the timer
final String value = osugg.getValue();
if (OptionSuggestion.NEXT_VALUE.equals(value)) {
mindexFrom += PAGE_SIZE;
mindexTo += PAGE_SIZE;
final RestSuggestOracle oracle = (RestSuggestOracle) mfield.getSuggestOracle();
oracle.getSuggestions();
} else if (OptionSuggestion.PREVIOUS_VALUE.equals(value)) {
mindexFrom -= PAGE_SIZE;
mindexTo -= PAGE_SIZE;
final RestSuggestOracle oracle = (RestSuggestOracle) mfield.getSuggestOracle();
oracle.getSuggestions();
} else {
// made a valid selection
updateFormFeedback(FormFeedback.VALID, null);
// add the option's value to the value map
putValue(osugg.getName(), value);
// put the focus back into the textfield so user
// can enter more
// Commented mfield.setFocus(true);
}
}
}
private void putValue(final String key, final String value) {
Log.info("putting key = " + key + "; value = " + value);
mvalueMap.put(key, value);
}
/**
* Retrieve Options (name-value pairs) that are suggested from the REST
* endpoint
*
* @param query
* - the String search term
* @param from
* - the 0-based begin index int
* @param to
* - the end index inclusive int
* @param callback
* - the OptionQueryCallback to handle the response
*/
private void queryOptions(final String query, final int from, final int to,
final OptionQueryCallback callback) {
final RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(mrestEndpointUrl
+ "?" + SearcherConstants.QUERY_PARAM + "=" + query + "&" + SearcherConstants.START_PARAM + "="
+ from + "&" + SearcherConstants.LIMIT_PARAM + "=" + PAGE_SIZE));
// Set our headers
builder.setHeader("Accept", "application/json; charset=utf-8");
// Fails on chrome
// builder.setHeader("Accept-Charset", "UTF-8");
builder.setCallback(new RequestCallback() {
@Override
public void onError(final com.google.gwt.http.client.Request request, final Throwable exception) {
callback.error(exception);
}
@Override
public void onResponseReceived(final com.google.gwt.http.client.Request request,
final Response response) {
final JSONValue val = JSONParser.parse(response.getText());
final JSONObject obj = val.isObject();
final int totSize = (int) obj.get(OptionResultSet.TOTAL_SIZE).isNumber().doubleValue();
final OptionResultSet options = new OptionResultSet(totSize);
final JSONArray optionsArray = obj.get(OptionResultSet.OPTIONS).isArray();
if (options.getTotalSize() > 0 && optionsArray != null) {
for (int i = 0; i < optionsArray.size(); i++) {
if (optionsArray.get(i) == null) {
/*
* This happens when a JSON array has an invalid trailing comma
*/
continue;
}
final JSONObject jsonOpt = optionsArray.get(i).isObject();
final Option option = new Option();
final String longName = jsonOpt.get(OptionResultSet.DISPLAY_NAME).isString().stringValue();
final String shortName = jsonOpt.get(OptionResultSet.VALUE).isString().stringValue();
final JSONValue groupTypeJsonValue = jsonOpt.get("groupType");
final String prefix = groupTypeJsonValue.isString() == null ? ""
: GroupType.PERSONAL.name().equals(groupTypeJsonValue.isString().stringValue()) ? I18n.t("User")
+ ": "
: I18n.t("Group") + ": ";
option.setName(prefix
+ (!longName.equals(shortName) ? longName + " (" + shortName + ")" : shortName));
option.setValue(jsonOpt.get(OptionResultSet.VALUE).isString().stringValue());
options.addOption(option);
}
}
callback.success(options);
}
});
try {
if (lastQuery != null && lastQuery.isPending()) {
lastQuery.cancel();
}
lastQuery = builder.send();
} catch (final RequestException e) {
updateFormFeedback(FormFeedback.ERROR, "Error: " + e.getMessage());
}
}
private void resetPageIndices() {
mindexFrom = 0;
mindexTo = mindexFrom + PAGE_SIZE - 1;
}
@Override
public void setAccessKey(final char key) {
mfield.setAccessKey(key);
}
@Override
public void setFocus(final boolean focused) {
mfield.setFocus(focused);
}
public void setSearchUrl(final String searchUrl) {
mrestEndpointUrl = searchUrl;
}
@Override
public void setTabIndex(final int index) {
mfield.setTabIndex(index);
}
/**
* Convenience method to set the status and tooltip of the FormFeedback
*
* @param status
* - a FormFeedback status
* @param tooltip
* - a String tooltip
*/
public void updateFormFeedback(final int status, final String tooltip) {
mfeedback.setStatus(status);
if (tooltip != null) {
mfeedback.setTitle(tooltip);
}
final TextBoxBase textBox = mfield.getTextBox();
if (FormFeedback.LOADING == status) {
NotifyUser.showProgressSearching();
// textBox.setEnabled(false);
} else {
new Timer() {
@Override
public void run() {
NotifyUser.hideProgress();
}
}.schedule(1500);
// textBox.setEnabled(true);
textBox.setFocus(false); // Blur then focus b/c of a strange problem with
// the cursor or selection highlights no longer
// visible within the textfield (this is a
// workaround)
textBox.setFocus(true);
}
}
}